/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../smc_tbls.h"
#include "../smc.h"

#include "../regs.h"

#include "ctx.h"
#include "private.h"
#include "smc_state_tbl.h"
#include "smc_lvl.h"
#include "smc_volt.h"
#include "smc_mc_reg_tbl.h"
#include "smc_mc_arb_tbl.h"
#include "smc_sw_regs.h"

static void smc_sw_state_init(struct ctx *ctx, struct smc_sw_state *state)
{
	state->flgs = SMC_SW_STATE_FLGS_DC;
	state->lvls_n = ctx->atb_performance.lvls_n;
}

#define PWR_EFFICIENCY_RATIO_MARGIN 10
static u16 pwr_efficiency_ratio_compute(struct ctx *ctx, 
		struct smc_volt *prev_std_vddc, struct smc_volt *cur_std_vddc)
{
	u64 prev_std_vddc_mv;
	u64 cur_std_vddc_mv;
	u64 n;
	u64 d;
	u64 pwr_efficiency_ratio;

	prev_std_vddc_mv = (u64)get_unaligned_be16(&prev_std_vddc->val);
	cur_std_vddc_mv = (u64)get_unaligned_be16(&cur_std_vddc->val);

	n = div64_u64(1024 * cur_std_vddc_mv * cur_std_vddc_mv
				* (1000 + PWR_EFFICIENCY_RATIO_MARGIN), 1000);
	d = prev_std_vddc_mv * prev_std_vddc_mv;

	pwr_efficiency_ratio = div64_u64(n, d);
	return (u16)pwr_efficiency_ratio;
}

#define NEAR_TDP_DEC		10
#define ABOVE_SAFE_INC		5
#define BELOW_SAFE_INC		20
static long smc_pp_dpm_to_perf_lvl_init(struct ctx *ctx, u8 lvl_idx,
				struct smc_pp_dpm_to_perf_lvl *dpm_to_perf_lvl)
{
	long r;
	struct atb_pp_lvl *prev_atb_lvl;
	struct atb_pp_lvl *cur_atb_lvl;
	struct smc_volt prev_vddc;
	struct smc_volt cur_vddc;
	struct smc_volt prev_std_vddc;
	struct smc_volt cur_std_vddc;
	u16 pwr_efficiency_ratio;

	if (lvl_idx == 0) {
		dpm_to_perf_lvl->max_pulse_skip = 0;
		dpm_to_perf_lvl->near_tdp_dec = 0;
		dpm_to_perf_lvl->above_safe_inc = 0;
		dpm_to_perf_lvl->below_safe_inc = 0;
		put_unaligned_be16(0, &dpm_to_perf_lvl->pwr_efficiency_ratio);
		return 0;
	}

	/* from here lvl_idx >= 1 */

	/*--------------------------------------------------------------------*/

	dpm_to_perf_lvl->max_pulse_skip = 0; /* because no uvd */
	dpm_to_perf_lvl->near_tdp_dec = NEAR_TDP_DEC ;
	dpm_to_perf_lvl->above_safe_inc = ABOVE_SAFE_INC;
	dpm_to_perf_lvl->below_safe_inc = BELOW_SAFE_INC;

	/*--------------------------------------------------------------------*/

	prev_atb_lvl = &ctx->atb_performance.lvls[lvl_idx - 1];
	r = smc_volt_vddc_set_from_atb_id(ctx, &prev_vddc,
							prev_atb_lvl->vddc_id);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to find/set previous vddc_id\n");
		return -SI_ERR;
	}
	smc_volt_std_vddc_compute(ctx, &prev_std_vddc, &prev_vddc);

	cur_atb_lvl = &ctx->atb_performance.lvls[lvl_idx];
	r = smc_volt_vddc_set_from_atb_id(ctx, &cur_vddc, cur_atb_lvl->vddc_id);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to find/set current vddc_id\n");
		return -SI_ERR;
	}
	smc_volt_std_vddc_compute(ctx, &cur_std_vddc, &cur_vddc);

	pwr_efficiency_ratio = pwr_efficiency_ratio_compute(ctx, &prev_std_vddc,
								&cur_std_vddc);

	put_unaligned_be16(pwr_efficiency_ratio,
					&dpm_to_perf_lvl->pwr_efficiency_ratio);
	return 0;
}

#define SQ_RAMPING_PWR_MAX		0x3fff
#define SQ_RAMPING_PWR_MIN		0x12
#define SQ_RAMPING_PWR_DELTA_MAX	0x15
#define SQ_RAMPING_STI_SZ		0x1e
#define SQ_RAMPING_LTI_RATIO		0xf
static void sq_ramping_threshold_init(struct ctx *ctx, u8 lvl_idx,
							struct smc_lvl *lvl)
{
	u32 sq_pwr_throttle_0;
	u32 sq_pwr_throttle_1;

	if (ctx->atb_performance.lvls[lvl_idx].eng_clk >=
					ctx->atb_sq_ramping_threshold) {
		sq_pwr_throttle_0 = set(SPT0_PWR_MAX, SQ_RAMPING_PWR_MAX);
		sq_pwr_throttle_0 |= set(SPT0_PWR_MIN, SQ_RAMPING_PWR_MIN);

		sq_pwr_throttle_1 = set(SPT1_PWR_DELTA_MAX,
						SQ_RAMPING_PWR_DELTA_MAX);
		sq_pwr_throttle_1 |= set(SPT1_STI_SZ, SQ_RAMPING_STI_SZ);
		sq_pwr_throttle_1 |= set(SPT1_LTI_RATIO, SQ_RAMPING_LTI_RATIO);
	} else {
		sq_pwr_throttle_0 = SPT0_PWR_MIN | SPT0_PWR_MAX;
		sq_pwr_throttle_1 = SPT1_PWR_DELTA_MAX | SPT1_STI_SZ
							| SPT1_LTI_RATIO;
	}

	put_unaligned_be32(sq_pwr_throttle_0, &lvl->sq_pwr_throttle_0);
	put_unaligned_be32(sq_pwr_throttle_1, &lvl->sq_pwr_throttle_1);
}

#define AH_DEFAULT 5
#define HW_PWR_LVLS_N_MAX 5
static void a_t_compute(struct ctx *ctx, u8 lvl_idx, u32 *t_l, u32 *t_h)
{
	u32 k;
	u32 a;
	u32 a_h;
	u32 a_l;
	u32 t1;
	u32 t;
	u32 h;
	u32 freq_high;
	u32 freq_low;

	t = (50 / HW_PWR_LVLS_N_MAX) * 100 * (lvl_idx + 1),
	h = 100 * AH_DEFAULT;
	freq_high = ctx->atb_performance.lvls[lvl_idx + 1].eng_clk;
	freq_low = ctx->atb_performance.lvls[lvl_idx].eng_clk;

	k = (100 * freq_high) / freq_low;
	t1 = (t * (k - 100));
	a = (1000 * (100 * h + t1)) / (10000 + (t1 / 100));
	a = (a + 5) / 10;
	a_h = ((a * t) + 5000) / 10000;
	a_l = a - a_h;

	*t_h = t - a_h;
	*t_l = t + a_l;
}

static void a_t_init(struct ctx *ctx, u8 lvl_idx, struct smc_lvl *lvl)
{
	u8 lvl_idx_last;
	u32 t_l;
	u32 t_h;
	u32 a_t;
	struct smc_lvl *next_lvl;
	u8 next_lvl_idx;
	u32 high_bs_p;

	lvl_idx_last = ctx->atb_performance.lvls_n - 1;
	if (lvl_idx == lvl_idx_last) /* because a_t is set by pair of lvls */
		return;

	a_t_compute(ctx, lvl_idx, &t_l, &t_h);

	/*--------------------------------------------------------------------*/

	a_t = get_unaligned_be32(&lvl->a_t);
	a_t &= ~CAT_CG_R;
	a_t |= set(CAT_CG_R, t_l * ctx->bs_p / 20000);
	put_unaligned_be32(a_t, &lvl->a_t);

	/*--------------------------------------------------------------------*/

	next_lvl = lvl + 1;
	next_lvl_idx = lvl_idx + 1;

	if (next_lvl_idx == lvl_idx_last)
		high_bs_p = ctx->p_bs_p;
	else
		high_bs_p = ctx->bs_p;

	a_t = set(CAT_CG_R, 0xffff) | set(CAT_CG_L, t_h * high_bs_p / 20000);
	put_unaligned_be32(a_t, &next_lvl->a_t);

	/*--------------------------------------------------------------------*/
}

/*
 * carefull since some fields are computed from one lvl and the next, or
 * from one lvl and the prev
 */
static long smc_lvls_init(struct ctx *ctx, struct smc_lvl *lvls)
{
	u8 lvl_idx;
	u8 lvl_idx_last;
	u8 a_t_init_done;

	/* a_t has a special setting when only on pwr lvl is there */
	a_t_init_done = 0;
	if (ctx->atb_performance.lvls_n == 1) {
		u32 a_t;

		a_t = set(CAT_CG_R, 0xffff) | set(CAT_CG_L, 0);
		put_unaligned_be32(a_t, &lvls[0].a_t);
		a_t_init_done = 1;
	}

	/* the first lvl of the pwr state has some special ds setting */
	lvls[0].state_flgs = (u8)SMC_STATE_FLGS_DEEPSLEEP_BYPASS;

	/*
 	 * the last lvl of the pwr state will get some special settings in the
 	 * following loop
 	 */ 
	lvl_idx_last = ctx->atb_performance.lvls_n - 1;	

	for (lvl_idx = 0; lvl_idx < ctx->atb_performance.lvls_n; ++lvl_idx) {
		struct smc_lvl *lvl;
		long r;

		lvl = &lvls[lvl_idx];

		r = smc_lvl_from_atb(ctx, &ctx->atb_performance.lvls[lvl_idx],
								&lvls[lvl_idx]);
		if (r == -SI_ERR)
			goto err;

		if (lvl_idx == lvl_idx_last) {
			lvl->disp_watermark = SMC_DISP_WATERMARK_HIGH;
			put_unaligned_be32(ctx->p_sp, &lvl->b_sp);
		} else {
			lvl->disp_watermark = SMC_DISP_WATERMARK_LOW;
			put_unaligned_be32(ctx->d_sp, &lvl->b_sp);
		}

		lvl->mc_reg_set_idx = MC_REG_SET_IDX_DRIVER + lvl_idx;
		lvl->mc_arb_set_idx = MC_ARB_SET_IDX_DRIVER + lvl_idx;

		smc_pp_dpm_to_perf_lvl_init(ctx, lvl_idx,
							&lvl->dpm_to_perf_lvl);
		sq_ramping_threshold_init(ctx, lvl_idx, lvl);

		if (!a_t_init_done)
			a_t_init(ctx, lvl_idx, lvl);
	}
	return 0;
err:
	return -SI_ERR;
}

/* return the *atb* set idx, the smc set idx */
static u8 atb_mc_reg_set_find(struct ctx *ctx, u32 mem_clk)
{
	u8 mc_reg_set_idx;

	/* select a set of mc regs which can support the pwr lvl mem clk */
	for (mc_reg_set_idx = 0; mc_reg_set_idx < ctx->atb_mc_reg_tbl.sets_n;
							++mc_reg_set_idx) {
		struct atb_mc_reg_set *mc_reg_set;

		mc_reg_set = &ctx->atb_mc_reg_tbl.sets[mc_reg_set_idx];

		if (mem_clk <= mc_reg_set->mem_clk_max)
			break;
	}

	/*
	 * Not found, then try the last one as a work around which should
	 * accomodate the highest mem clk. The tbl seems to be sorted
	 * from lowest mem clk to highest mem clk.
	 */
	if (mc_reg_set_idx == ctx->atb_mc_reg_tbl.sets_n)
		--mc_reg_set_idx; /* we presume we have at least one set */
	return mc_reg_set_idx;
}

static void smc_mc_reg_set_init(struct ctx *ctx, u8 smc_set_idx,
						struct smc_mc_reg_set *set)
{
	u32 mem_clk;
	u8 atb_mc_reg_set_idx;

	mem_clk = ctx->atb_performance.lvls[smc_set_idx].mem_clk;

	atb_mc_reg_set_idx = atb_mc_reg_set_find(ctx, mem_clk);

	smc_mc_reg_set_load(ctx, atb_mc_reg_set_idx, set);
}

static void smc_mc_reg_sets_init(struct ctx *ctx, struct smc_mc_reg_set *sets)
{
	u8 set_idx;

	for (set_idx = 0; set_idx < ctx->atb_performance.lvls_n; ++set_idx)
		smc_mc_reg_set_init(ctx, set_idx, &sets[set_idx]);
}

static long smc_mc_arb_reg_set_init(struct ctx *ctx, u8 smc_set_idx,
						struct smc_mc_arb_reg_set *set)
{
	u32 eng_clk;
	u32 mem_clk;

	eng_clk = ctx->atb_performance.lvls[smc_set_idx].eng_clk;
	mem_clk = ctx->atb_performance.lvls[smc_set_idx].mem_clk;

	return smc_mc_arb_tbl_set_compute(ctx, set, eng_clk, mem_clk);
}

static long smc_mc_arb_reg_sets_init(struct ctx *ctx,
						struct smc_mc_arb_reg_set *sets)
{
	u8 set_idx;

	for (set_idx = 0; set_idx < ctx->atb_performance.lvls_n; ++set_idx) {
		long r;

		r = smc_mc_arb_reg_set_init(ctx, set_idx, &sets[set_idx]);
		if (r == -SI_ERR)
			return -SI_ERR;
	}
	return 0;
}

static void tbls_cpy(struct ctx *ctx,
				struct smc_sw_state *smc_sw_state,
				struct smc_lvl *smc_lvls,
				struct smc_mc_reg_set *smc_mc_reg_sets,
				struct smc_mc_arb_reg_set *smc_mc_arb_reg_sets)
{
	u32 smc_major_tbl_of;
	u32 smc_ram_tbl_of;
#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	struct smc_state_tbl *smc_state_tbl;
	struct smc_mc_reg_tbl *smc_mc_reg_tbl;
	struct smc_mc_arb_tbl *smc_mc_arb_tbl;

	smc_state_tbl = kzalloc(sizeof(*smc_state_tbl), GFP_KERNEL);
	if (!smc_state_tbl)
		dev_warn(&ctx->dev->dev, "dyn_pm:log:unable to alloc some ram for smc_state_tbl dump\n");
	smc_mc_reg_tbl = kzalloc(sizeof(*smc_mc_reg_tbl), GFP_KERNEL);
	if (!smc_mc_reg_tbl)
		dev_warn(&ctx->dev->dev, "dyn_pm:log:unable to alloc some ram for smc_mc_reg_tbl dump\n");
	smc_mc_arb_tbl = kzalloc(sizeof(*smc_mc_arb_tbl), GFP_KERNEL);
	if (!smc_mc_arb_tbl)
		dev_warn(&ctx->dev->dev, "dyn_pm:log:unable to alloc some ram for smc_mc_arb_tbl dump\n");
#endif
	
	LOG("PERFORMANCE STATE TABLES START");

	/*--------------------------------------------------------------------*/

	smc_major_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
							+ SMC_FW_HDR_STATE_TBL);
	smc_ram_tbl_of = smc_major_tbl_of + offsetof(struct smc_state_tbl,
									driver);

	LOG("DRIVER SMC_SW_STATE OFFSET=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_sw_state,
							sizeof(*smc_sw_state));

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_major_tbl_of + offsetof(struct smc_state_tbl,
								driver_lvls);

	LOG("DRIVER SMC_LVLS OFFSET=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_lvls, sizeof(*smc_lvls)
						* ctx->atb_performance.lvls_n);

	/*--------------------------------------------------------------------*/

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	if (smc_state_tbl) {
		smc_memcpy_from(ctx->dev, (u8*)smc_state_tbl, smc_major_tbl_of,
							sizeof(*smc_state_tbl));
		smc_state_tbl_dump(smc_state_tbl);
		kfree(smc_state_tbl);
	}
#endif

	/*--------------------------------------------------------------------*/

	smc_major_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
							+ SMC_FW_HDR_MC_TBL);

	smc_ram_tbl_of = smc_major_tbl_of + offsetof(struct smc_mc_reg_tbl,
									sets);
	smc_ram_tbl_of += MC_REG_SET_IDX_DRIVER * sizeof(*smc_mc_reg_sets);
	LOG("DRIVER SMC_MC_REG_SETS OFFSET=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_mc_reg_sets,
			sizeof(*smc_mc_reg_sets) * ctx->atb_performance.lvls_n);

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	if (smc_mc_reg_tbl) {
		smc_memcpy_from(ctx->dev, (u8*)smc_mc_reg_tbl, smc_major_tbl_of,
						sizeof(*smc_mc_reg_tbl));
		smc_mc_reg_tbl_dump(smc_mc_reg_tbl);
		kfree(smc_mc_reg_tbl);
	}
#endif

	/*--------------------------------------------------------------------*/

	smc_major_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
						+ SMC_FW_HDR_MC_ARB_TBL);

	smc_ram_tbl_of = smc_major_tbl_of + offsetof(struct smc_mc_arb_tbl,
									sets);
	smc_ram_tbl_of += MC_ARB_SET_IDX_DRIVER * sizeof(*smc_mc_arb_reg_sets);
	LOG("DRIVER SMC_MC_ARB_REG_SET OFFSET=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_mc_arb_reg_sets,
		sizeof(*smc_mc_arb_reg_sets) * ctx->atb_performance.lvls_n);

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	if (smc_mc_arb_tbl) {
		smc_memcpy_from(ctx->dev, (u8*)smc_mc_arb_tbl, smc_major_tbl_of,
						sizeof(*smc_mc_arb_tbl));
		smc_mc_arb_tbl_dump(smc_mc_arb_tbl);
		kfree(smc_mc_arb_tbl);
	}
#endif

	/*--------------------------------------------------------------------*/

	LOG("PERFORMANCE STATE TABLES END");
}

static long tbls_init(struct ctx *ctx)
{
	struct smc_sw_state *smc_sw_state;
	struct smc_lvl *smc_lvls;
	struct smc_mc_reg_set *smc_mc_reg_sets;
	struct smc_mc_arb_reg_set *smc_mc_arb_reg_sets;
	long r;

	/*--------------------------------------------------------------------*/

	smc_sw_state = kzalloc(sizeof(*smc_sw_state), GFP_KERNEL);
	if (!smc_sw_state) {
		dev_err(&ctx->dev->dev, "dyn_pm:driver:unable to alloc the smc_sw_tbl\n");
		goto err;
	}

	smc_sw_state_init(ctx, smc_sw_state);

	/*--------------------------------------------------------------------*/

	smc_lvls = kzalloc(sizeof(*smc_lvls) * ctx->atb_performance.lvls_n,
								GFP_KERNEL);
	if (!smc_lvls) {
		dev_err(&ctx->dev->dev, "dyn_pm:driver:unable to alloc the smc_lvls\n");
		goto err_free_smc_sw_state;
	}

	r = smc_lvls_init(ctx, smc_lvls); 
	if (r == -SI_ERR)
		goto err_free_smc_lvls;

	/*--------------------------------------------------------------------*/

	smc_mc_reg_sets = kzalloc(sizeof(*smc_mc_reg_sets)
				* ctx->atb_performance.lvls_n, GFP_KERNEL);
	if (!smc_mc_reg_sets) {
		dev_err(&ctx->dev->dev, "dyn_pm:driver:unable to alloc the smc_mc_reg_sets\n");
		goto err_free_smc_lvls;
	}

	smc_mc_reg_sets_init(ctx, smc_mc_reg_sets);
	
	/*--------------------------------------------------------------------*/

	smc_mc_arb_reg_sets = kzalloc(sizeof(*smc_mc_arb_reg_sets)
				* ctx->atb_performance.lvls_n, GFP_KERNEL);
	if (!smc_mc_arb_reg_sets) {
		dev_err(&ctx->dev->dev, "dyn_pm:driver:unable to alloc the smc_mc_arb_reg_sets\n");
		goto err_free_smc_mc_reg_sets; 
	}

	r = smc_mc_arb_reg_sets_init(ctx, smc_mc_arb_reg_sets);
	if (r == -SI_ERR)
		goto err_free_smc_mc_arb_reg_sets;
	
	/*--------------------------------------------------------------------*/

	tbls_cpy(ctx, smc_sw_state, smc_lvls, smc_mc_reg_sets,
							smc_mc_arb_reg_sets);

	kfree(smc_mc_arb_reg_sets);
	kfree(smc_mc_reg_sets);	
	kfree(smc_lvls);
	kfree(smc_sw_state);
	return 0;

err_free_smc_mc_arb_reg_sets:
	kfree(smc_mc_arb_reg_sets);

err_free_smc_mc_reg_sets:
	kfree(smc_mc_reg_sets);

err_free_smc_lvls:
	kfree(smc_lvls);

err_free_smc_sw_state:
	kfree(smc_sw_state);

err:
	return -SI_ERR;
}

static void sw_regs_init(struct ctx *ctx)
{
	u8 lvl_idx_last;
	u32 eng_clk_max;

	lvl_idx_last = ctx->atb_performance.lvls_n - 1;
	eng_clk_max = ctx->atb_performance.lvls[lvl_idx_last].eng_clk;
	smc_sw_wr32(ctx->dev, eng_clk_max / 512, SMC_SW_WATERMARK_THRESHOLD);
}

long driver_set_performance(struct ctx *ctx)
{
	long r;

	LOG("smc:switching off the use of the ultra low voltage state");
	r = smc_msg(ctx->dev, SMC_MSG_ULV_DIS);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch off the use of the ultra low voltage state\n");
		return -SI_ERR;
	}

	LOG("smc:disable power levels switching");
	r = smc_msg(ctx->dev, SMC_MSG_NO_FORCE_LVL);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to disable power level switching\n");
		return -SI_ERR;
	}

	LOG("smc:restrict to one powel level");
	r = smc_msg_param(ctx->dev, SMC_MSG_ENA_LVLS_N_SET, 1);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to restrict to one power level\n");
		return -SI_ERR;
	}

	/* TODO: may have to set the best pcie gen of the power state here */

	LOG("smc:switching off the thermal design power clamping");
	r = smc_msg(ctx->dev, SMC_TDP_CLAMPING_OFF);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch off the thermal design power clamping\n");
		return -SI_ERR;
	}

	LOG("smc:switching off the digital temperature estimation");
	r = smc_msg(ctx->dev, SMC_MSG_DTE_DIS);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch off the digital temperature estimation\n");
		return -SI_ERR;
	}

	LOG("smc:switching off calculation accumulator");
	r = smc_msg(ctx->dev, SMC_MSG_CAC_DIS);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch off the calculation accumulator\n");
		return -SI_ERR;
	}

	LOG("smc:switching off the long term average calculation accumulator");
	r = smc_msg(ctx->dev, SMC_MSG_CAC_LONG_TERM_AVG_DIS);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch off the long term average calculation accumulator\n");
		return -SI_ERR;
	}

	LOG("smc:halt");
	r = smc_msg(ctx->dev, SMC_MSG_HALT);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:failed to halt\n");
		return -SI_ERR;
	}

	LOG("smc:waiting to halt");
	smc_halt_wait(ctx->dev);

	/*--------------------------------------------------------------------*/

	r = tbls_init(ctx);
	if (r == -SI_ERR)
		return -SI_ERR;

	sw_regs_init(ctx);
#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	smc_sw_regs_dump(ctx->dev);
#endif

	/*--------------------------------------------------------------------*/

	LOG("smc:flushing data cache");
	r = smc_msg(ctx->dev, SMC_MSG_DATA_CACHE_FLUSH);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to flush the data cache\n");
		return -SI_ERR;
	}

	LOG("smc:resume");
	r = smc_msg(ctx->dev, SMC_MSG_RESUME);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to resume operations\n");
		return -SI_ERR;
	}

	LOG("smc:switch to driver(sw) power state");
	r = smc_msg(ctx->dev, SMC_MSG_SW_STATE_SWITCH);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch to driver(sw) power state\n");
		return -SI_ERR;
	}

	if (ctx->atb_ulv.lvls_n) {
		/*
		 * the mem clk of the lowest driver state lvl should be the same
		 * than the ulv one
		 */
		LOG("smc:enable ultra low voltage state");
		r = smc_msg(ctx->dev, SMC_MSG_ULV_ENA);
		if (r == -SI_ERR) {
			dev_err(&ctx->dev->dev, "dyn_pm:init:smc:failed to enable the ultra low voltage state\n");
			return -SI_ERR;
		}
	} else {
		LOG("smc:disable ultra low voltage state");
		r = smc_msg(ctx->dev, SMC_MSG_ULV_DIS);
		if (r == -SI_ERR) {
			dev_err(&ctx->dev->dev, "dyn_pm:init:smc:failed to disable the ultra low voltage state\n");
			return -SI_ERR;
		}
	}

	LOG("smc:switching on the long term average calculation accumulator");
	r = smc_msg(ctx->dev, SMC_MSG_CAC_LONG_TERM_AVG_ENA);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch on the long term average calculation accumulator\n");
		return -SI_ERR;
	}

	LOG("smc:switching on calculation accumulator");
	r = smc_msg(ctx->dev, SMC_MSG_CAC_ENA);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch on the calculation accumulator\n");
		return -SI_ERR;
	}

	LOG("smc:switching on the digital temperature estimation");
	r = smc_msg(ctx->dev, SMC_MSG_DTE_ENA);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch on the digital temperature estimation\n");
		return -SI_ERR;
	}

	LOG("smc:switching on the thermal design power clamping");
	r = smc_msg(ctx->dev, SMC_TDP_CLAMPING_ON);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to switch on the thermal design power clamping\n");
		return -SI_ERR;
	}

	/*
	 * XXX: some systems may use the chip without any display connected to
	 * it. But some boards don't like ito have the smc told there are
	 * active displays without any. Waiting upstream to fix this
	 */
	LOG("smc:disable low power state switch when there is no display");
	r = smc_msg(ctx->dev, SMC_MSG_HAS_DISPLAY);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:init:smc:unable to disable the switch to low power state when there is no display\n");
		return -SI_ERR;
	}
	return 0;
}
